/**
 * @file exponential_distr.h
 *
 * The class to represent a exponential distribution
 * 
 * @author Dahua Lin
 */

#ifndef SLIPP_EXPONENTIAL_DISTR_H_
#define SLIPP_EXPONENTIAL_DISTR_H_

#include <slipp/prob_base/pdistr_base.h>

namespace sli
{

	template<class RStream>
	class exponential_sampler
	{
	public:
		SLIPP_DEFINE_SCALAR_DISTR_TYPES(double)

	public:
		exponential_sampler(RStream& rstream, const scalar_type& beta)
		: m_rng(rstream), m_beta(beta)
		{
		}

		sample_type draw()
		{
			return m_rng.get() * m_beta;
		}

		sample_collection_type draw(size_t n)
		{
			sample_collection_type v((index_t)n);
			m_rng.get_m(n, v.data());
			return v * m_beta;
		}

	private:
		exponential_rng<RStream> m_rng;
		double m_beta;
	};


	class exponential_distr
	{
	public:
		SLIPP_DEFINE_SCALAR_DISTR_TYPES(double)

		class param_type
		{
		public:
			param_type() : m_beta(1.0) { }

			param_type(const scalar_type& beta)
			: m_beta(beta) { }

			scalar_type beta() const
			{
				return m_beta;
			}

			scalar_type lambda() const
			{
				return math::rcp(m_beta);
			}

			bool operator == (const param_type& rhs) const
			{
				return m_beta == rhs.m_beta;
			}

			bool operator != (const param_type& rhs) const
			{
				return !(operator == (rhs));
			}

		private:
			scalar_type m_beta;
		};

	public:
		// constructors

		exponential_distr()
		: m_param()
		{
		}

		exponential_distr(const scalar_type& beta)
		: m_param(beta)
		{
		}

		exponential_distr(const param_type& param)
		: m_param(param)
		{
		}

		// parameters

		const param_type& param() const
		{
			return m_param;
		}

		scalar_type beta() const
		{
			return m_param.beta();
		}

		scalar_type lambda() const
		{
			return m_param.lambda();
		}

		// statistics

		double mean() const
		{
			return beta();
		}

		double var() const
		{
			return math::sqr(beta());
		}

		double entropy() const
		{
			return 1.0 + math::log(beta());
		}

	public:
		class pdf_evaluator_type
		{
		public:
			typedef normal_distr::sample_type sample_type;
			typedef normal_distr::sample_collection_type sample_collection_type;

			pdf_evaluator_type(const param_type& param)
			: m_lambda(param.lambda())
			{
			}

			SLIPP_ENSURE_INLINE double logpdf(const scalar_type& x) const
			{
				return x >= 0 ? math::log(m_lambda) - m_lambda * x : - std::numeric_limits<double>::infinity();
			}

			SLIPP_ENSURE_INLINE double pdf(const scalar_type& x) const
			{
				return x >= 0 ? m_lambda * math::exp(-m_lambda * x) : 0.0;
			}

			darow_t pdf(const sample_collection_type& X) const
			{
				return map(_detail::make_pdf_eval_functor(*this), X);
			}

			darow_t logpdf(const sample_collection_type& X) const
			{
				return map(_detail::make_logpdf_eval_functor(*this), X);
			}

		private:
			scalar_type m_lambda;
		};

		pdf_evaluator_type get_pdf_evaluator() const
		{
			return pdf_evaluator_type(m_param);
		}

	public:
		// sampling

		template<class RStream>
		exponential_sampler<RStream> get_sampler(RStream& rstream) const
		{
			return exponential_sampler<RStream>(rstream, beta());
		}

	private:
		param_type m_param;

	}; // end class exponential_distr


	// traits

	SLIPP_DEFINE_BASIC_DISTR_TRAITS(exponential_distr, exponential_sampler)


}

#endif 
